本章节的讲解代码下载地址如下:
tips复制代码链接: https://pan.baidu.com/s/1SyC6kv9LhlTG3IThP4aIQQ 提取码: 5ws6
承接前文,本章节会继续讲解微服务架构下订单微服务的编码改造。章节源码是在newbee-mall-cloud-dev-step15工程的基础上改造,工程命名为newbee-mall-cloud-dev-step16。
创建订单微服务模块
与其它微服务模块的创建过程一致,首先要创建订单微服务模块。在工程中新建一个newbee-mall-cloud-order-service模块,并在主pom.xml文件中增加该模块的配置,代码如下:
xml复制代码<modules>
<!-- 新增订单微服务 -->
<module>newbee-mall-cloud-order-service</module>
<module>newbee-mall-cloud-shop-cart-service</module>
<module>newbee-mall-cloud-recommend-service</module>
<module>newbee-mall-cloud-goods-service</module>
<module>newbee-mall-cloud-user-service</module>
<module>newbee-mall-cloud-gateway-mall</module>
<module>newbee-mall-cloud-gateway-admin</module>
<module>newbee-mall-cloud-common</module>
</modules>
该模块的目录结构设置与其它的功能模块类似,如下所示:
java复制代码newbee-mall-cloud-order-service // 订单微服务
├── newbee-mall-cloud-order-api // 存放订单模块中暴露出去的用于远程调用的FeignClient类
└── newbee-mall-cloud-order-web // 订单API的代码及逻辑
新建订单微服务模块时,主要是参考了当时新建商品微服务时的步骤。笔者是直接将newbee-mall-cloud-dev-step05源码下newbee-mall-cloud-goods-service模块中的代码复制了过来,之后将模块名称和目录名称修改掉。
由于改造过程中是直接复制了newbee-mall-cloud-dev-step05源码下newbee-mall-cloud-goods-service模块中的代码,所以在修改完依赖配置后,就是修改包名,把ltd.goods.cloud.xxx的命名改为ltd.order.cloud.xxx,然后修改config包中的代码,包括全局异常处理配置类、Swagger配置类、自定义MVC配置类,主要是修改了这些类的类名。这样,就完成了一个订单微服务的初始构建工作。
订单微服务中的用户身份认证也需要进行处理,过程与之前微服务中的处理步骤类似。由于订单微服务中的接口涉及到商城端和后台管理系统,所以商城端用户和管理员用户两种身份相关的逻辑代码都要复制过来。接着,需要引入loadbalancer依赖和user-api依赖,并修改商城端用户和管理员用户token处理类中的逻辑代码,并通过OpenFeign来调用用户微服务中的接口,在订单微服务中实现两种用户的鉴权和信息获取了。
订单微服务编码
接下来,开始补充订单微服务中的业务代码,主要是把原来单体项目中的功能模块整合到订单微服务中。
订单微服务代码改造
打开订单微服务newbee-mall-cloud-order-web的工程目录,在ltd.order.cloud.newbee包下依次创建config包、dao包、entity包、service包,在resources目录下新增mapper文件夹用于存放Mapper文件。接着,直接将原单体API项目中订单模块相关的业务代码和Mapper文件依次复制进来即可,如下图所示。
上述步骤完成后,最终的代码目录如下图所示。
最后,修改newbee-mall-cloud-order-web工程中的application.properties配置文件,主要是数据库连接参数及MyBatis扫描配置,代码如下所示:
properties复制代码# datasource config (MySQL) spring.datasource.name=newbee-mall-cloud-order-datasource spring.datasource.driverClassName=com.mysql.cj.jdbc.Driver spring.datasource.url=jdbc:mysql://localhost:3306/newbee_mall_cloud_order_db?useUnicode=true&serverTimezone=Asia/Shanghai&characterEncoding=utf8&autoReconnect=true&useSSL=false spring.datasource.username=root spring.datasource.password=123456 spring.datasource.hikari.minimum-idle=5 spring.datasource.hikari.maximum-pool-size=15 spring.datasource.hikari.auto-commit=true spring.datasource.hikari.idle-timeout=60000 spring.datasource.hikari.pool-name=hikariCP spring.datasource.hikari.max-lifetime=600000 spring.datasource.hikari.connection-timeout=30000 spring.datasource.hikari.connection-test-query=SELECT 1 # mybatis config mybatis.mapper-locations=classpath:mapper/*Mapper.xml
本步骤中的源码,涉及到的数据库为newbee_mall_cloud_order_db,数据库表为tb_newbee_mall_order、tb_newbee_mall_order_item、tb_newbee_mall_user_address、tb_newbee_mall_order_address,开发过程中需要用到。除此之外,也会用到商品数据和用户数据,这部分数据就要使用远程调用技术了。
代码修改注意事项
在订单微服务的代码调整前,有些工具类已经被移动到公共模块newbee-mall-cloud-common中,所以在pom.xml中需要引入公共模块。同时,代码中使用到这些工具类的地方也需要处理一下引用路径。
除此之外,订单微服务中也有一些公共类,都放到了公共模块newbee-mall-cloud-common中。
Controller类中的接口地址都做了微调,与之前的URL不同。调整的原因主要是网关配置时方便一些。
网关模块配置
打开商城端网关newbee-mall-cloud-gateway-mall项目中的application.properties文件,新增关于订单微服务的路由信息,配置项为spring.cloud.gateway.routes.*,新增内容如下:
properties复制代码# 订单接口的路由配置 spring.cloud.gateway.routes[5].id=order-service-route spring.cloud.gateway.routes[5].uri=lb://newbee-mall-cloud-order-service spring.cloud.gateway.routes[5].order=1 spring.cloud.gateway.routes[5].predicates[0]=Path=/orders/mall/** # 收货地址接口的路由配置 spring.cloud.gateway.routes[6].id=order-service-route2 spring.cloud.gateway.routes[6].uri=lb://newbee-mall-cloud-order-service spring.cloud.gateway.routes[6].order=1 spring.cloud.gateway.routes[6].predicates[0]=Path=/mall/address/**
如果访问到网关项目的路径是以/orders/mall/和/mall/address/开头,就路由到订单微服务实例。
打开后台管理系统网关newbee-mall-cloud-gateway-admin项目中的application.properties文件,新增关于订单微服务的路由信息,配置项为spring.cloud.gateway.routes.*,新增内容如下:
properties复制代码# 订单接口的路由配置 spring.cloud.gateway.routes[5].id=order-service-route spring.cloud.gateway.routes[5].uri=lb://newbee-mall-cloud-order-service spring.cloud.gateway.routes[5].order=1 spring.cloud.gateway.routes[5].predicates[0]=Path=/orders/admin/**
如果访问到网关项目的路径是以/orders/admin开头,就路由到订单微服务实例。
订单微服务远程调用商品微服务和购物车微服务编码实践
按照上述步骤对订单微服务进行编码后,代码中依然会标红。被标红的代码及注释如下所示:
java复制代码@PostMapping("/saveOrder")
@ApiOperation(value = "生成订单接口", notes = "传参为地址id和待结算的购物项id数组")
public Result<String> saveOrder(@ApiParam(value = "订单参数") @RequestBody SaveOrderParam saveOrderParam, @TokenToMallUser MallUser loginMallUser) {
int priceTotal = 0;
if (saveOrderParam == null || saveOrderParam.getCartItemIds() == null || saveOrderParam.getAddressId() == null) {
NewBeeMallException.fail(ServiceResultEnum.PARAM_ERROR.getResult());
}
if (saveOrderParam.getCartItemIds().length < 1) {
NewBeeMallException.fail(ServiceResultEnum.PARAM_ERROR.getResult());
}
// 根据购物项id列表查询购物项列表并进行基本的逻辑验证
List<NewBeeMallShoppingCartItemVO> itemsForSave = newBeeMallShoppingCartService.getCartItemsForSettle(Arrays.asList(saveOrderParam.getCartItemIds()), loginMallUser.getUserId());
... 省略部分代码
return ResultGenerator.genFailResult("生成订单失败");
}
在生成订单接口中,需要根据cartItemIds参数查询数据库中的一条或者多条购物项数据进行基本的逻辑验证,然后才能进行后续的业务逻辑。而订单微服务未连接购物项表所在的数据库,所以无法直接查询对应的购物项数据,这部分代码会标红。
java复制代码@Override
@Transactional
public String saveOrder(MallUser loginMallUser, MallUserAddress address, List<NewBeeMallShoppingCartItemVO> myShoppingCartItems) {
List<Long> itemIdList = myShoppingCartItems.stream().map(NewBeeMallShoppingCartItemVO::getCartItemId).collect(Collectors.toList());
List<Long> goodsIds = myShoppingCartItems.stream().map(NewBeeMallShoppingCartItemVO::getGoodsId).collect(Collectors.toList());
// 查询商品数据
List<NewBeeMallGoods> newBeeMallGoods = newBeeMallGoodsMapper.selectByPrimaryKeys(goodsIds);
// 检查是否包含已下架商品
List<NewBeeMallGoods> goodsListNotSelling = newBeeMallGoods.stream()
.filter(newBeeMallGoodsTemp -> newBeeMallGoodsTemp.getGoodsSellStatus() != Constants.SELL_STATUS_UP)
.collect(Collectors.toList());
if (!CollectionUtils.isEmpty(goodsListNotSelling)) {
// goodsListNotSelling 对象非空则表示有下架商品
NewBeeMallException.fail(goodsListNotSelling.get(0).getGoodsName() + "已下架,无法生成订单");
}
Map<Long, NewBeeMallGoods> newBeeMallGoodsMap = newBeeMallGoods.stream().collect(Collectors.toMap(NewBeeMallGoods::getGoodsId, Function.identity(), (entity1, entity2) -> entity1));
// 判断商品库存
for (NewBeeMallShoppingCartItemVO shoppingCartItemVO : myShoppingCartItems) {
// 查出的商品中不存在购物车中的这条关联商品数据,直接返回错误提醒
if (!newBeeMallGoodsMap.containsKey(shoppingCartItemVO.getGoodsId())) {
NewBeeMallException.fail(ServiceResultEnum.SHOPPING_ITEM_ERROR.getResult());
}
// 存在数量大于库存的情况,直接返回错误提醒
if (shoppingCartItemVO.getGoodsCount() > newBeeMallGoodsMap.get(shoppingCartItemVO.getGoodsId()).getStockNum()) {
NewBeeMallException.fail(ServiceResultEnum.SHOPPING_ITEM_COUNT_ERROR.getResult());
}
}
// 删除购物项
if (!CollectionUtils.isEmpty(itemIdList) && !CollectionUtils.isEmpty(goodsIds) && !CollectionUtils.isEmpty(newBeeMallGoods)) {
if (newBeeMallShoppingCartItemMapper.deleteBatch(itemIdList) > 0) {
List<StockNumDTO> stockNumDTOS = BeanUtil.copyList(myShoppingCartItems, StockNumDTO.class);
// 修改商品库存
int updateStockNumResult = newBeeMallGoodsMapper.updateStockNum(stockNumDTOS);
if (updateStockNumResult < 1) {
NewBeeMallException.fail(ServiceResultEnum.SHOPPING_ITEM_COUNT_ERROR.getResult());
}
//生成订单号
String orderNo = NumberUtil.genOrderNo();
int priceTotal = 0;
//保存订单
NewBeeMallOrder newBeeMallOrder = new NewBeeMallOrder();
newBeeMallOrder.setOrderNo(orderNo);
newBeeMallOrder.setUserId(loginMallUser.getUserId());
//总价
for (NewBeeMallShoppingCartItemVO newBeeMallShoppingCartItemVO : myShoppingCartItems) {
priceTotal += newBeeMallShoppingCartItemVO.getGoodsCount() * newBeeMallShoppingCartItemVO.getSellingPrice();
}
if (priceTotal < 1) {
NewBeeMallException.fail(ServiceResultEnum.ORDER_PRICE_ERROR.getResult());
}
newBeeMallOrder.setTotalPrice(priceTotal);
String extraInfo = "";
... 省略部分代码
NewBeeMallException.fail(ServiceResultEnum.DB_ERROR.getResult());
}
NewBeeMallException.fail(ServiceResultEnum.DB_ERROR.getResult());
}
NewBeeMallException.fail(ServiceResultEnum.SHOPPING_ITEM_ERROR.getResult());
return ServiceResultEnum.SHOPPING_ITEM_ERROR.getResult();
}
同样的,在生成订单的业务层代码中,也有针对购物项表和商品表的操作,比如删除购物项数据、查询商品数据、修改商品库存,之后才能进行后续生成订单的业务逻辑。而订单微服务未连接购物项表和商品表所在的数据库,所以无法直接通过NewBeeMallShoppingCartItemMapper和GoodsMapper去完成对应的功能,这部分代码会标红。
因此,想要完成订单微服务的代码改造,必须要进行购物项数据查询、购物项数据删除、商品数据查询和商品库存修改部分代码的改造,由原本直接操作购物项表和商品表改为远程调用购物车微服务和商品微服务中的接口来完成这个逻辑。订单微服务不止要与用户微服务通信,还要与购物车微服务和商品微服务通信。这里的代码改造步骤如下所示。
第一步,在商品微服务增加修改库存的逻辑代码和对外暴露接口。
当前所需的查询购物项数据、删除购物项数据、查询商品数据和修改商品库存这四个远程调用操作,其中有3个都已经完成编码了,只有修改商品库存还未完成编码。
打开newbee-mall-cloud-goods-web工程,在NewBeeAdminGoodsInfoController类中新增接口如下:
java复制代码/**
* 修改商品库存
*/
@PutMapping("/updateStock")
@ApiOperation(value = "修改库存", notes = "")
public Result updateStock(@RequestBody UpdateStockNumDTO updateStockNumDTO) {
return ResultGenerator.genSuccessResult(newBeeMallGoodsService.updateStockNum(updateStockNumDTO.getStockNumDTOS()));
}
然后,打开newbee-mall-cloud-goods-api工程,在NewBeeCloudGoodsServiceFeign类中定义该接口使得其它微服务可以调用,新增代码如下:
java复制代码@PutMapping(value = "/admin/updateStock")
Result<Boolean> updateStock(@RequestBody UpdateStockNumDTO updateStockNumDTO);
第二步,引入goods-api和shop-cart-api依赖。
打开newbee-mall-cloud-order-web工程下的pom.xml文件,增加与购物车微服务和商品微服务远程通信所需的newbee-mall-cloud-shop-cart-api模块和newbee-mall-cloud-goods-api模块,新增依赖配置如下:
xml复制代码<dependency>
<groupId>ltd.goods.newbee.cloud</groupId>
<artifactId>newbee-mall-cloud-goods-api</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>ltd.shopcart.newbee.cloud</groupId>
<artifactId>newbee-mall-cloud-shop-cart-api</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
第三步,增加关于购物车微服务和商品微服务中FeignClient的配置。
打开newbee-mall-cloud-goods-web工程,对启动类NewBeeMallCloudOrderServiceApplication上的@EnableFeignClients注解进行修改,增加对于NewBeeCloudShopCartServiceFeign和NewBeeCloudGoodsServiceFeign的声明,代码如下:
java复制代码@EnableFeignClients(basePackageClasses ={ltd.goods.cloud.newbee.openfeign.NewBeeCloudGoodsServiceFeign.class, ltd.shopcart.cloud.newbee.openfeign.NewBeeCloudShopCartServiceFeign.class, ltd.user.cloud.newbee.openfeign.NewBeeCloudUserServiceFeign.class})
接下来就可以直接使用NewBeeCloudShopCartServiceFeign和NewBeeCloudGoodsServiceFeign与购物车微服务、商品微服务进行远程通信了。
第四步,修改对于商品表和购物项表操作的代码。
打开newbee-mall-cloud-order-web工程,修改NewBeeMallOrderServiceImpl类中saveOrder()方法中的逻辑代码。删除原本直接操作购物项表和商品表的代码,然后依次注入NewBeeCloudShopCartServiceFeign类和NewBeeCloudGoodsServiceFeign类,并通过调用购物车微服务和商品微服务来完成查询购物项数据、删除购物项数据、查询商品数据和修改商品库存四个逻辑操作。这里,笔者把购物项数据的查询和前置判断也转移到业务方法中了。修改后的代码及相应的注释如下所示:
java复制代码@Autowired
private NewBeeCloudGoodsServiceFeign goodsService;
@Autowired
private NewBeeCloudShopCartServiceFeign shopCartService;
@Override
@Transactional
public String saveOrder(Long mallUserId, MallUserAddress address, List<Long> cartItemIds) {
//调用购物车服务feign获取数据
Result<List<NewBeeMallShoppingCartItemDTO>> cartItemDTOListResult = shopCartService.listByCartItemIds(cartItemIds);
if (cartItemDTOListResult == null || cartItemDTOListResult.getResultCode() != 200) {
NewBeeMallException.fail("参数异常");
}
List<NewBeeMallShoppingCartItemDTO> itemsForSave = cartItemDTOListResult.getData();
if (CollectionUtils.isEmpty(itemsForSave)) {
//无数据
NewBeeMallException.fail("参数异常");
}
List<Long> itemIdList = itemsForSave.stream().map(NewBeeMallShoppingCartItemDTO::getCartItemId).collect(Collectors.toList());
List<Long> goodsIds = itemsForSave.stream().map(NewBeeMallShoppingCartItemDTO::getGoodsId).collect(Collectors.toList());
//调用商品服务feign获取数据
Result<List<NewBeeMallGoodsDTO>> goodsDTOListResult = goodsService.listByGoodsIds(goodsIds);
if (goodsDTOListResult == null || goodsDTOListResult.getResultCode() != 200) {
NewBeeMallException.fail("参数异常");
}
List<NewBeeMallGoodsDTO> newBeeMallGoods = goodsDTOListResult.getData();
//检查是否包含已下架商品
List<NewBeeMallGoodsDTO> goodsListNotSelling = newBeeMallGoods.stream()
.filter(newBeeMallGoodsTemp -> newBeeMallGoodsTemp.getGoodsSellStatus() != 0)
.collect(Collectors.toList());
if (!CollectionUtils.isEmpty(goodsListNotSelling)) {
//goodsListNotSelling 对象非空则表示有下架商品
NewBeeMallException.fail(goodsListNotSelling.get(0).getGoodsName() + "已下架,无法生成订单");
}
Map<Long, NewBeeMallGoodsDTO> newBeeMallGoodsMap = newBeeMallGoods.stream().collect(Collectors.toMap(NewBeeMallGoodsDTO::getGoodsId, Function.identity(), (entity1, entity2) -> entity1));
//判断商品库存
for (NewBeeMallShoppingCartItemDTO cartItemDTO : itemsForSave) {
//查出的商品中不存在购物车中的这条关联商品数据,直接返回错误提醒
if (!newBeeMallGoodsMap.containsKey(cartItemDTO.getGoodsId())) {
NewBeeMallException.fail(ServiceResultEnum.SHOPPING_ITEM_ERROR.getResult());
}
//存在数量大于库存的情况,直接返回错误提醒
if (cartItemDTO.getGoodsCount() > newBeeMallGoodsMap.get(cartItemDTO.getGoodsId()).getStockNum()) {
NewBeeMallException.fail(ServiceResultEnum.SHOPPING_ITEM_COUNT_ERROR.getResult());
}
}
//删除购物项
if (!CollectionUtils.isEmpty(itemIdList) && !CollectionUtils.isEmpty(goodsIds) && !CollectionUtils.isEmpty(newBeeMallGoods)) {
//调用购物车服务feign删除数据
Result<Boolean> deleteByCartItemIdsResult = shopCartService.deleteByCartItemIds(itemIdList);
if (deleteByCartItemIdsResult != null && deleteByCartItemIdsResult.getResultCode() == 200) {
List<StockNumDTO> stockNumDTOS = BeanUtil.copyList(itemsForSave, StockNumDTO.class);
UpdateStockNumDTO updateStockNumDTO = new UpdateStockNumDTO();
updateStockNumDTO.setStockNumDTOS(stockNumDTOS);
//调用商品服务feign修改库存数据
Result<Boolean> updateStockResult = goodsService.updateStock(updateStockNumDTO);
if (updateStockResult == null || updateStockResult.getResultCode() != 200) {
NewBeeMallException.fail(ServiceResultEnum.PARAM_ERROR.getResult());
}
if (!updateStockResult.getData()) {
NewBeeMallException.fail(ServiceResultEnum.SHOPPING_ITEM_COUNT_ERROR.getResult());
}
... 省略部分代码
NewBeeMallException.fail(ServiceResultEnum.DB_ERROR.getResult());
}
NewBeeMallException.fail(ServiceResultEnum.DB_ERROR.getResult());
}
NewBeeMallException.fail(ServiceResultEnum.SHOPPING_ITEM_ERROR.getResult());
return ServiceResultEnum.SHOPPING_ITEM_ERROR.getResult();
}
功能测试
代码修改完成后,不要觉得就万事大吉了,测试步骤是不能漏掉的。一定要验证项目是否能正常启动,接口是否能正常调用,防止在代码移动过程中出现的一些小问题导致项目无法启动或者代码报错。在项目启动前需要启动Nacos Server和Redis Server,然后依次启动newbee-mall-cloud-order-web工程、newbee-mall-cloud-shop-cart-web工程、newbee-mall-cloud-user-web工程、newbee-mall-cloud-goods-web工程下的主类(如果想要测试网关功能还需要再启动两个网关模块下的主类)。启动成功后,就可以进行本章节的功能测试了。
首先打开用户微服务的Swagger界面,在浏览器中输入如下地址:
http://localhost:29000/swagger-ui/index.html
之后在该页面使用商城用户的登录接口获取一个token值用于后续的功能测试,比如笔者在测试时获取到了一个“c5b0a720e8068a186d9d3ff7ff3a28d8”的token值。
接下来打开订单微服务的Swagger界面,在浏览器中输入如下地址:
http://localhost:29040/swagger-ui/index.html
接着,就可以在Swagger提供的UI页面进行订单微服务中的接口测试了,页面显示内容如下。
主要包括后台管理系统订单模块接口、个人地址模块接口和商城端订单操作相关接口。由于篇幅限制,这里只对商城端订单操作相关接口进行测试。
添加收货地址接口演示
下单时需要用户的收货地址信息,否则是无法正确的生成订单数据。点开“添加地址”,并点击Try it out按钮,并点击Try it out按钮,在参数栏填入收货地址的相关信息,在登录认证token的输入框中输入登录接口返回的Token字段值,如下图所示。
点击Execute按钮,接口响应结果如下图所示。
后端接口响应为“SUCCESS”则表示收货地址信息添加成功。此时,再去订单微服务的数据库中查看收货地址表中的数据,可以看到已经新增了一条地址信息了,该数据的主键id为2164,后续生成订单时会用到。
生成订单接口演示
点开“生成订单接口”,并点击Try it out按钮,并点击Try it out按钮,在参数栏填入当前用户的地址id和需要结算的购物项id列表,这里填入的数据都是刚刚演示时生成的数据(地址id为2164,购物项id为购物车微服务实战章节生成的数据,分别是7625和7626)。在登录认证token的输入框中输入登录接口返回的Token字段值,如下图所示。
点击Execute按钮,接口响应结果如下图所示。
如果结算时提交的数据都正确,就可以得到一个订单生成后的订单号字段,该字段的值在响应对象Result的data字段中,比如当前接口的测试结果,获取了值为“16629533442214515”的订单号,之后就能够使用该订单号来测试取消订单、模拟支付、订单详情都接口了。生成订单接口测试成功。
订单列表接口演示
点开“订单列表接口”,并点击Try it out按钮,并点击Try it out按钮,在参数栏填入页码和订单状态字段,在登录认证token的输入框中输入登录接口返回的Token字段值,就可以去查询当前用户的订单列表数据了,如下图所示。
点击Execute按钮,接口响应结果如下图所示。
请求成功。订单列表中所需的数据在Result类的data属性中,其中有分页信息、定案列表数据,每一条购物项中包括订单号、订单状态、下单时间、订单中包含的商品等内容。
这三个接口,对应到实际的项目页面中,是新蜂商城项目的添加收货地址、确认订单页面和订单列表页面,显示效果如下图所示。
至此,下单流程中的部分功能就演示完成了,读者在测试时可以关注一下MySQL数据库中购物项表的变化。
功能测试完成且接口响应一切正常,表示订单微服务本身的功能编码完成,且远程调用用户微服务、商品微服务、购物车微服务也一切正常。在测试时,读者也可以通过debug模式启动项目,然后打上几个断点来查看接口测试时的完整过程。由于篇幅限制,笔者这里只演示了部分接口的测试过程,读者在测试时可以看看其它接口。除了在订单微服务项目的Swagger页面测试接口外,也可以通过商城端网关来访问这些接口进行功能测试。
总结
本章节中主要是订单模块在微服务架构下的编码改造,在订单微服务开发完成后,本课程实战项目已经完成了原单体商城项目中所有功能模块的开发与测试。当然,这不是终点,后续依然会补充一些必要的知识点进来。希望各位读者能够根据笔者提供的开发步骤顺利地完成本章节的项目修改。当然,读者如果有任何问题或者想要和笔者讨论的内容,都可以在评论区留下看法,笔者会根据读者的反馈和问题继续整理和完善本章节内容。